Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Missing take and optimize some captures #347

Merged
merged 9 commits into from
Feb 15, 2024

Conversation

to11mtm
Copy link
Member

@to11mtm to11mtm commented Feb 10, 2024

May help with #346

Changes

  • Fixes missing TAKE on EventsByTag Query for Tag table case.
  • Provides overload for ExecuteInTransactionAsync that takes input state to minimize captures
  • Fixed a few spots where we weren't using capture-minimizing APIs completely
  • Tried to do a few other capture related optimizations and remove a double-prop access on read paths.

Checklist

For significant changes, please ensure that the following have been completed (delete if not relevant):

Benchmarks

Will see what I can do...

@to11mtm to11mtm force-pushed the fix-take-and-captures branch from 8276437 to e94254b Compare February 10, 2024 18:02
Copy link
Member Author

@to11mtm to11mtm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self review with some food for thought.

}
).Via(_deserializeFlow);
}


internal async Task<List<JournalRow>> ExecuteEventQuery(QueryArgs queryArgs)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentionally moved into it's own method,

Among other things, Another memory optimization for us to consider is -not- using Akka streams in the Unfold internal read calls; Where this gets tricky, is that marshalling via Akka streams pipeline, neatly solves problems where a lot of DB Providers aren't really all that async 🙃.

SQLite (Fair enough,) I think some oracle providers, IDK about MySQL...

I can fold back in for now if needed, but it does make the pipeline at least a little bit neater.

(jr, jtr) => (jr.Ordering == jtr.OrderingId),
(jr, jtr) => jr)
.OrderBy(r => r.Ordering)
.Take(txInput.Max);
Copy link
Member Author

@to11mtm to11mtm Feb 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, We didn't have a take here.

However, AFAIR, the LINQ query syntax tends to be more finicky with captures/closures than (carefully) plotted out L2Db Method syntax. However, if the lambda syntax somehow is producing something needed for a specific DB, we can go back to it. NVM It's not playing nice and we can deal with that later.

src/Akka.Persistence.Sql/Query/Dao/QueryArgs.cs Outdated Show resolved Hide resolved
@to11mtm to11mtm requested review from CumpsD and Arkatufus February 10, 2024 18:37
Comment on lines 54 to +57
_cloneConnection = new Lazy<AkkaDataConnection>(
() => new AkkaDataConnection(
_opts.ConnectionOptions.ProviderName,
new DataConnection(_opts)));
opts.ConnectionOptions.ProviderName,
new DataConnection(opts)));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

subtle thing; now we are capturing just the DataOptions reference rather than the factory itself...

IDK how much the GC really cares but it sounds like the sort of thing that can annoy a cycle detector,

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

from lp in tagTable.Where(jtr => jtr.OrderingId == r.Ordering).DefaultIfEmpty()
where lp.OrderingId > input.offset &&
lp.OrderingId <= input.maxOffset &&
from r in connection.GetTable<JournalRow>()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would still be nice to convert this to method syntax later, if only because it's something like 300 lines of IL as it stands 😅

Comment on lines +77 to +83
internal static Task<T> ExecuteWithTransactionAsync<TState,T>(
this DbStateHolder factory,
TState state,
Func<AkkaDataConnection, CancellationToken, TState, Task<T>> handler)
{
return factory.ConnectionFactory.ExecuteWithTransactionAsync(state, factory.IsolationLevel, factory.ShutdownToken, handler);
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convenience method; since we are using the stateholder to avoid other captures, may as well add some sugar to go with it and make the main DAO cleaner.


internal static async Task<List<JournalRow>> ExecuteEventQuery(DbStateHolder stateHolder, TagMode tagMode, QueryArgs queryArgs)
{
return tagMode != TagMode.TagTable
Copy link
Member Author

@to11mtm to11mtm Feb 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is ugly and IDK how I feel but it's the easiest way to get more captures out of the way, short of some more significant refactoring where we had some different ways to compose without going back to Func<> madness...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is ugly

Use an if statement instead of a ternary operator and it'll look better, I swear :p

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment that was somehow left unposted, I wound up moving these back into separate methods.

Copy link
Member

@Aaronontheweb Aaronontheweb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally LGTM, but did leave some comments

Comment on lines 54 to +57
_cloneConnection = new Lazy<AkkaDataConnection>(
() => new AkkaDataConnection(
_opts.ConnectionOptions.ProviderName,
new DataConnection(_opts)));
opts.ConnectionOptions.ProviderName,
new DataConnection(opts)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

async (connection, token) =>
return await input._dbStateHolder.ExecuteWithTransactionAsync(
input.maxTake,
async (connection, token,take) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just return a Task here instead of async / await ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm honestly not sure, concerns aside from potential stack trace confusion on errors would be whether the fact this func is actually invoked inside two using contexts... Better safe than sorry when it comes to that stuff IMO.

Copy link
Contributor

@Arkatufus Arkatufus Feb 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some discussions on this are here:

Would the performance boost for each query execution be worth it, does it outweight the possible exception stack trace confusion?

From my experience, the "proper" stack trace from an exception thrown from within an awaited Task is confusing in itself, since it is thrown from inside the async...await FSM and most often it will be a detached from the actual code that invoked it (when the thrown exception is caught by the debugger), so I can't really say that it is an actual improvement in debugging experience. But then again, I could be doing it wrong.

On the other hand, the article does point out that using proper async puts the exception where it belongs, in the future when it is supposed to happen, if it indeed needs to be thrown.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to agree with @to11mtm on this one, returning a Task from inside a using block is quite dangerous because we don't know just how robust the IDispose is being implemented by the 3rd party API developers.


internal static async Task<List<JournalRow>> ExecuteEventQuery(DbStateHolder stateHolder, TagMode tagMode, QueryArgs queryArgs)
{
return tagMode != TagMode.TagTable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is ugly

Use an if statement instead of a ternary operator and it'll look better, I swear :p

src/Akka.Persistence.Sql/Query/Dao/DbStateHolder.cs Outdated Show resolved Hide resolved
@to11mtm
Copy link
Member Author

to11mtm commented Feb 14, 2024

@Aaronontheweb if you and @Arkatufus are OK with this feel free to merge, otherwise LMK what you would like to see changed. <3

Copy link
Contributor

@Arkatufus Arkatufus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Arkatufus Arkatufus enabled auto-merge (squash) February 15, 2024 16:10
@Arkatufus Arkatufus merged commit fe49a6b into akkadotnet:dev Feb 15, 2024
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants